const crypto = require('crypto');

/**
 * 签名工具类
 */
class SignUtils {
    // 白名单keys
    static whiteParamKeys = ['sign'];


    /**
     * 获取请求签名参数
     * @param {*} request 
     * @returns {Object}
     */
    static getRequestSignParams(ctx) {
        const headers = ctx.headers;
        const query = ctx.query;
        const body = ctx.request.body || {};
        const params = ctx.params || {};
        const result = {
            // 必选参数
            _sign_timestamp: headers['timestamp'],
            _sign_nonce: headers['nonce'],
            _sign_url_param: Object.values(params).join(','),
            // 业务参数
            ...query,
            ...body
        };
        return result;
    }

    /**
     * 生成签名
     * @param {Object} params 参数对象
     * 备注：params中必须包含这几个参数，headers中也应该包含：（timestamp、nonce）
     * @param {string} params._sign_timestamp 时间戳
     * @param {string} params._sign_nonce 随机数
     * @param {string} params._sign_url_param url参数（多个以","隔开）
     * 
     * @param {string} secretKey 秘钥
     * @param {string} algorithm 
     */
    static generateSign(params, secretKey, algorithm = 'sha256') {
        // 过滤掉签名参数和空值参数
        const filteredParams = {};
        for (const key in params) {
            if (!this.whiteParamKeys.includes(key)) {
                const value = params[key];
                if (![null, undefined, ''].includes(value)) {
                    filteredParams[key] = value;
                }
            }
        }
        // 按字典序排序参数
        const sortedKeys = Object.keys(filteredParams).sort();
        const sortedParams = {};
        sortedKeys.forEach(key => {
            sortedParams[key] = filteredParams[key];
        });
        // 拼接键值对
        const stringToSign = this._stringifyParams(sortedParams);
        // 生成签名
        const hmac = crypto.createHmac(algorithm, secretKey);
        hmac.update(stringToSign);
        return hmac.digest('hex');
    }

    /**
     * 验证签名
     * @param {*} param0.sign 客户端的签名 
     * @param {*} param0.params 参数 
     * @param {*} param0.secretKey 秘钥 
     * @param {*} param0.algorithm 客户端的签名 
     * @returns 
     */
    static verifySign({ sign, params, secretKey, algorithm } = {}) {
        if (!sign) {
            return false;
        }
        params = params || {};
        const serverSign = this.generateSign(params, secretKey, algorithm);
        return sign === serverSign;
    }

    /**
     * 将参数对象转为签名字符串
     * @param {*} params 
     * @returns 
     */
    static _stringifyParams(params) {
        return Object.keys(params)
            .map(key => {
                const value = params[key];
                if (typeof value === 'object' && value !== null) {
                    return `${key}=${JSON.stringify(value)}`;
                }
                return `${key}=${value}`;
            })
            .join('&');
    }
}

/**
 * 防重放攻击工具类
 */
class NonceUtils {
    // 默认有效期，5分钟
    static defaultValidDuration = 5 * 60 * 1000;

    /**
     * 检查时间戳是否有效，默认
     * @param {*} timestamp 时间戳（毫秒）
     * @param {*} validDuration 有效时长（毫秒），默认5分钟
     */
    static checkTimestamp(timestamp, validDuration) {
        validDuration = validDuration || this.defaultValidDuration;
        const now = Date.now();
        return Math.abs(now - timestamp) <= validDuration;
    }
}


module.exports = { SignUtils, NonceUtils }